package com.ybear.ybnetworkutil.http;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ybear.ybnetworkutil.call.CallDownloadListener;
import com.ybear.ybnetworkutil.call.CallReqListener;
import com.ybear.ybnetworkutil.call.Callback;
import com.ybear.ybnetworkutil.call.CallbackString;
import com.ybear.ybnetworkutil.request.Method;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

/**
 * 发起请求
 */
public class ClientBuilder {
    private final OkHttpClient mClient;
    private final Request.Builder mReqBuild;
    private final BodyType mBodyType;
    private final Body mBody;
    private final com.ybear.ybnetworkutil.request.Request mRequest;
    private boolean mEnableAutoFillHttp = true;
    private boolean mEnableAutoReplaceHttps = false;
    @Nullable
    private final CallReqListener mCallReqListener;

    private File mTmpDownloadFile;
    private int mDownloadBuffSize = 4096;

    private ClientBuilder(@NonNull OkHttpClient client,
                  @NonNull Request.Builder builder,
                  @NonNull com.ybear.ybnetworkutil.request.Request request,
                  @Nullable CallReqListener call) {
        if( request.getBodyType() != null ) {
            mBodyType = request.getBodyType();
        }else {
            mBodyType = BodyType.create();
        }
        mBody = new Body( this, request, mBodyType );
        mClient = client;
        mReqBuild = builder;
        mRequest = request;
        mCallReqListener = call;
    }

    public static ClientBuilder newBuilder(@NonNull OkHttpClient client,
                                           @NonNull Request.Builder builder,
                                           @NonNull com.ybear.ybnetworkutil.request.Request request,
                                           @Nullable CallReqListener call) {
        return new ClientBuilder( client, builder, request, call );
    }

    public Body mediaTypeForm() {
        return mBody.mediaTypeForm();
    }

    public Body mediaTypeJson() {
        return mBody.mediaTypeJson();
    }

    public Body mediaTypeJs() {
        return mBody.mediaTypeJs();
    }

    public Body mediaTypeXml() {
        return mBody.mediaTypeXml();
    }

    public Body mediaTypeTextPlain() {
        return mBody.mediaTypeTextPlain();
    }

    public Body mediaTypeTextXml() {
        return mBody.mediaTypeTextXml();
    }

    public Body mediaTypeTextHtml() {
        return mBody.mediaTypeTextHtml();
    }

    /**
     启用/禁用自动填充http
     @param enable      是否启用
     @return            {@link ClientBuilder#mBody}
     */
    public Body setEnableAutoFillHttp(boolean enable) {
        mEnableAutoFillHttp = enable;
        return mBody;
    }

    /**
     启用/禁用自动替换http为https
     @param enable      是否启用
     @return            {@link ClientBuilder#mBody}
     */
    public Body setAutoReplaceHttps(boolean enable) {
        this.mEnableAutoReplaceHttps = enable;
        return mBody;
    }

    /**
     * 异步Get请求
     */
    public void get() { req( Method.GET, false ); }
    /**
     * 同步Get请求
     * @return  结果
     */
    @Nullable
    public Response getSync() { return req( Method.GET, true ); }

    /**
     * 异步Post请求
     */
    public void post() { req( Method.POST, false ); }
    /**
     * 同步Post请求
     * @return  结果
     */
    @Nullable
    public Response postSync() { return req( Method.POST, true ); }

    /**
     * 异步Put请求
     */
    public void put() { req( Method.PUT, false ); }
    /**
     * 同步Put请求
     * @return  结果
     */
    @Nullable
    public Response putSync() { return req( Method.PUT, true ); }

    /**
     * 异步Delete请求
     */
    public void delete() { req( Method.DELETE, false ); }
    /**
     * 同步Delete请求
     * @return  结果
     */
    @Nullable
    public Response deleteSync() { return req( Method.DELETE, true ); }

    /**
     * 异步Head请求
     */
    public void head() { req( Method.HEAD, false ); }
    /**
     * 同步Head请求
     * @return  结果
     */
    @Nullable
    public Response headSync() { return req( Method.HEAD, true ); }

    /**
     * 异步Patch请求
     */
    public void patch() { req( Method.PATCH, false ); }
    /**
     * 同步Patch请求
     * @return  结果
     */
    @Nullable
    public Response patchSync() { return req( Method.PATCH, true ); }

    /**
     下载请求
     @param saveFile        保存的文件位置
     @return                this
     */
    public Response download(@NonNull File saveFile, int buff) {
        mTmpDownloadFile = saveFile;
        mDownloadBuffSize = buff;
        return req( Method.DOWNLOAD, false );
    }
    public Response download(@NonNull File saveFile) {
        return download( saveFile, mDownloadBuffSize );
    }

    /**
     下载请求
     @param savePath        保存的文件位置
     @return                this
     */
    public Response download(@NonNull String savePath, int buff) {
        return download( new File( savePath ), buff );
    }
    public Response download(@NonNull String savePath) {
        return download( savePath, mDownloadBuffSize );
    }

    /**
     * 异步根据传入的{@link com.ybear.ybnetworkutil.request.Request#getMethod()} 决定调用方式
     */
    public void auto() { req( mRequest.getMethod(), false ); }

    /**
     * 同步根据传入的{@link com.ybear.ybnetworkutil.request.Request#getMethod()} 决定调用方式
     * @return  结果
     */
    public Response autoSync() { return req( mRequest.getMethod(), true ); }

    /**
     检查请求链接
     @param url     链接
     @return        1.是否检查自动填充Http
     */
    private String checkReqUrl(@NonNull String url) {

        //是否启用自动填充，检查是否存在http
        if( mEnableAutoFillHttp && !url.toLowerCase().trim().startsWith( "http" ) ) {
            url = "http://" + url;
        }
        //是否替换http为https
        if( mEnableAutoReplaceHttps && !url.startsWith( "https" ) ) {
            url = url.replaceFirst( "http" , "https" );
        }
        return url;
    }

    /**
     * 发起请求
     * @param method    请求类型
     * @param isSync    是否异步回调
     * @return          isSync 为 true 时返回，否则返回 null
     */
    private Response req(@Method String method, boolean isSync) {
        boolean isMediaType = Method.HEAD.equals( method ) || Method.GET.equals( method );
        //Head和Get拼接完整链接（ url + param ）
        String url = checkReqUrl( isMediaType ? mRequest.toFullUrl() : mRequest.toUrl() );
        Request.Builder b = isMediaType ?
                createMediaType( mReqBuild ).url( url ) :
                mReqBuild.url( url );

        RequestBody body = null;

        if( !isMediaType ) {
            //没有设置请求类型时默认使用requestBody
            if( mBodyType.build() == null ) mBody.requestBody();
            body = mBodyType.build();
            //请求体类型
            mRequest.setBodyType( mBodyType );
        }
        //当前请求类型
        mRequest.setMethod( method );

        //回调发起请求的Request
        onRequest( mRequest );

        switch ( method ) {
            case Method.POST:      //Post请求
                b.post( body );
                break;
            case Method.PUT:       //Put请求
                b.put( body );
                break;
            case Method.DELETE:    //Delete请求
                b.delete( body );
                break;
            case Method.PATCH:     //Patch请求
                b.patch( body );
                break;
            case Method.HEAD:      //Head请求
                b.head();
                break;
            case Method.DOWNLOAD:  //下载请求
                break;
            default:               //Get请求
                b.get();
                break;
        }
        //处理请求
        if( isSync ) {
            //同步处理
            return execute( b );
        }else {
            //异步处理
            return enqueue( b );
        }
    }

    /**
     * 异步处理
     * @param b     build
     * @return      null
     */
    private Response enqueue(Request.Builder b) {
        Call call = mClient.newCall( b.build() );
        //发起请求
        call.enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {
                //请求失败
                doOnFailure( call.request().url(), call, e );
            }
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response r) throws IOException {
                //请求成功
                doOnResponse( call.request().url(), call, r );
            }
        });
        return null;
    }

    /**
     * 同步处理
     * @param b     build
     * @return      结果
     */
    private Response execute(Request.Builder b) {
        try {
            Call clientCall = mClient.newCall( b.build() );
            Response result = clientCall.execute();
            ResponseBody body = result.body();
            HttpUrl url = result.request().url();
            if( body == null ) {
                IOException e = new IOException("Body is null.");
                //请求失败
                doOnFailure( url, clientCall, e );
            }else {
                //请求成功
                doOnResponse( url, clientCall, result );
            }
            return result;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 处理失败的请求
     * @param url                   地址
     * @param call                  回调
     * @param e                     异常
     */
    private void doOnFailure(@NonNull HttpUrl url, @NonNull Call call, @NonNull IOException e) {
        Callback callback;
        CallbackString callbackString;

        if( mRequest != null && mRequest.getCallDownloadListener() != null ) {
            doOnDownload( null, call, e );
            return;
        }

        //api自身的callback
        callback = mRequest != null ? mRequest.getCallback() : null;
        if( callback != null ) callback.onFailure( call, e );

        if( mCallReqListener != null ) {
            //全局Result
            mCallReqListener.onResult( url.toString(), null );
            //全局Failure
            mCallReqListener.onFailure( call, e );
        }
        //api自身的callback
        callbackString = mRequest != null ? mRequest.getCallbackString() : null;
        if( callbackString != null ) callbackString.onRequestString( null, false );
    }

    /**
     *
     * 处理成功的请求
     * @param url                   地址
     * @param call                  回调
     * @param r                     结果
     * @throws IOException          异常
     */
    private void doOnResponse(HttpUrl url, @NonNull Call call, @NonNull Response r) throws IOException {
        ResponseBody body = copyResponse( r ).body();
        Callback callback;
        CallbackString callbackString;

        //下载回调
        if( mRequest != null && mRequest.getCallDownloadListener() != null ) {
            doOnDownload( body, call, null );
            return;
        }
        //全局Response
        if( mCallReqListener != null ) mCallReqListener.onResponse( call, copyResponse( r ) );
        //api自身的callback
        callback = mRequest != null ? mRequest.getCallback() : null;
        if( callback != null ) callback.onResponse( call, copyResponse( r ) );

        //响应的字符串数据
        String bodyStr = body == null ? null : body.string();
        //全局Result
        if( mCallReqListener != null ) mCallReqListener.onResult( url.toString(), bodyStr );
        //api自身的callbackString
        callbackString = mRequest != null ? mRequest.getCallbackString() : null;
        if( callbackString != null ) callbackString.onRequestString( bodyStr, true );
    }

    private Response copyResponse(Response r) {
        return new Response.Builder( r ).build();
    }

    /**
     处理下载的文件
     @param body        响应Body
     @param call        回调
     @param e           异常
     */
    private void doOnDownload(ResponseBody body, Call call, Exception e) {
        if( mRequest == null ) return;
        CallDownloadListener listener = mRequest.getCallDownloadListener();
        if( mTmpDownloadFile == null ) {
            onDownloadFailure(
                    listener, call, new NullPointerException( "Empty save file location" )
            );
            return;
        }
        if( body == null ) {
            if( listener != null ) listener.onDownloadFailure( call, mTmpDownloadFile, e );
            mTmpDownloadFile = null;
            return;
        }
        InputStream is = null;
        byte[] buf = new byte[ mDownloadBuffSize ];
        int len;
        FileOutputStream fos = null;
        //文件不存在
        if( !mTmpDownloadFile.exists() ) {
            //获取文件路径
            String path = mTmpDownloadFile.getAbsolutePath();
            int separatorIndex = path.lastIndexOf( File.separator );
            if( separatorIndex > -1 ) {
                path = path.substring( 0, separatorIndex );
                File dirPath = new File( path );
                try {
                    //文件不存在时，创建目录，创建文件
                    if( !dirPath.exists() ) dirPath.mkdirs();
                    if( mTmpDownloadFile.createNewFile() ) {
                        is = body.byteStream();
                    }
                }catch(IOException ioException) {
                    ioException.printStackTrace();
                }
            }
        }else {
            is = body.byteStream();
        }
        //检查输入流
        if( is == null ) {
            onDownloadFailure(
                    listener, call, new NullPointerException( "Input stream creation failed" )
            );
            return;
        }
        try {
            //文件大小
            long total = body.contentLength();
            fos = new FileOutputStream( mTmpDownloadFile );
            long fileProgress = 0;
            while ((len = is.read(buf)) != -1) {
                fos.write( buf, 0, len );
                //已下载文件长度
                fileProgress += len;
                //百分比进度
                int progress = (int) ( fileProgress * 1.0f / total * 100 );
                //更新进度
                if (listener != null) {
                    listener.onDownloadProgress( call, progress, fileProgress, total );
                }
            }
            fos.flush();
            //下载完成
            if (listener != null) {
                listener.onDownloadComplete( call, new File( mTmpDownloadFile.getAbsolutePath() ) );
            }
        } catch (Exception e1) {
            onDownloadFailure( listener, call, e1 );
        }finally {
            try {
                is.close();
                if( fos != null ) fos.close();
            } catch (IOException e2) {
                e2.printStackTrace();
            }
        }
    }

    private void onDownloadFailure(CallDownloadListener l, Call call, Exception e) {
        if( l == null ) return;
        l.onDownloadFailure( call, mTmpDownloadFile, e );
        mTmpDownloadFile = null;
    }

    /**
     * 请求的Request
     * @param r             {@link com.ybear.ybnetworkutil.request.Request}
     */
    private void onRequest(com.ybear.ybnetworkutil.request.Request r) {
        if( mCallReqListener == null ) return;
        mCallReqListener.onRequest( r );
    }

    /**
     * 创建提交类型
     * @param b     build
     * @return      创建完成的build
     */
    private Request.Builder createMediaType(@NonNull Request.Builder b) {
        return b.addHeader("Content-Type", mBody.toMediaType() );
    }
}